package com.minelx.autocha;

import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class WebNode {
	private static final int INFINITE = -1;

	private final ChromeDriver driver;

	private final IElementLocation elementLocation;

	public WebNode(ChromeDriver driver, IElementLocation elementLocation) {
		this.driver = driver;
		this.elementLocation = elementLocation;
	}

	public void value(String content) {
		String value = content
				.replace("\"", "\\\"")
				.replace("\n", "\\n");
		driver.executeScript(elementLocation.querySelector() + ".value = \"" + value + "\"");
	}

	public String value() {
		return (String) driver.executeScript("return " + elementLocation.querySelector() + ".value");
	}

	public String text() {
		return webElement().getText();
	}

	public void text(String content) {
		String innerText = content
				.replace("\"", "\\\"")
				.replace("\n", "\\n");
		driver.executeScript(elementLocation.querySelector() + ".innerText = \"" + innerText + "\"");
	}

	public void attr(String name, String value) {
		driver.executeScript(String.format(elementLocation.querySelector() + ".setAttribute(\"%s\", \"%s\");", name, value));
	}

	public String attr(String name) {
		return webElement().getAttribute(name);
	}

	public void sendKeys(String keys) {
		must(webElement -> webElement.sendKeys(keys));
	}

	public void click() {
		must(WebElement::click);
	}

	private void must(Consumer<WebElement> what) {
		until(webElement -> true, what);
	}

	public void ifExists(Consumer<WebElement> what) {
		try {
			what.accept(webElement());
		} catch (StaleElementReferenceException | ElementNotInteractableException | NoSuchElementException ignored) {
			System.err.println("element isn't presented.");
		}
	}

	public <R> R waitForResult(Predicate<WebElement> condition,
	                           Function<WebElement, R> resultMapper) {
		until(condition);
		return resultMapper.apply(webElement());
	}

	public <R> R waitForResult(Predicate<WebElement> condition,
	                           int retryingCount,
	                           Function<WebElement, R> resultMapper,
	                           Function<WebElement, R> defaultSupplier) {
		try {
			until(condition, retryingCount);
			return resultMapper.apply(webElement());
		} catch (IllegalStateException ignored) {
			return defaultSupplier.apply(webElement());
		}
	}

	public void until(Predicate<WebElement> condition) {
		until(condition, webElement -> {
			// do nothing
		}, INFINITE);
	}

	public void until(Predicate<WebElement> condition, int retryingCount) {
		until(condition, webElement -> {
			// do nothing
		}, retryingCount);
	}

	public void until(Predicate<WebElement> condition, Consumer<WebElement> operation) {
		until(condition, operation, INFINITE);
	}

	public void until(Predicate<WebElement> condition, Consumer<WebElement> operation, int retryingCount) {
		while (!Thread.interrupted()) {
			if (retryingCount == 0) {
				throw new IllegalStateException("element is not presented");
			}
			retryingCount--;
			try {
				WebElement webElement = webElement();
				if (condition.test(webElement)) {
					operation.accept(webElement);
					break;
				}
			} catch (NoSuchElementException | StaleElementReferenceException | ElementNotInteractableException ignored) {
			}
			System.err.println(elementLocation.toSelector() + " element isn't presented, try it again later");
			sleepForAWhile();
		}
	}

	public WebElement webElement() {
		return driver.findElement(getElementLocation());
	}

	public static void sleepForAWhile() {
		sleep(1000);
	}

	public static void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException interruptedException) {
			throw new RuntimeException("force interruption", interruptedException);
		}
	}

	public By getElementLocation() {
		return elementLocation.getElementLocation();
	}
}
